#include "Components/ComponentInventoryBase.h"
#include "Managers/BlocksManager.h"
#include "Subsystems/SubsystemInventories.h"
#include "Subsystems/SubsystemPickables.h"

#include "Subsystems/BlockBehaviors/SubsystemBlockBehaviors.h"

#include "GameSingleton.h"

namespace PocketSurvival
{
    uint32_t ComponentInventoryBase::slotsCount()
    {
        return slots.size();
    }
    void ComponentInventoryBase::setSlotValue(uint32_t slotIndex, int32_t cellValue, uint32_t cellCount)
    {
        if (slotIndex < slots.size())
        {
            slots[slotIndex].value = cellValue;
            slots[slotIndex].count = cellCount;
            onSlotChange(slotIndex);
        }
    }
    int32_t ComponentInventoryBase::getSlotValue(uint32_t slotIndex)
    {
        if (slotIndex < slots.size())
        {
            if (slots[slotIndex].count < 0)
            {
                return 0;
            }
            return slots[slotIndex].value;
        }
        return 0;
    }
    uint32_t ComponentInventoryBase::getSlotCount(uint32_t slotIndex)
    {
        if (slotIndex < slots.size() && slots[slotIndex].value != 0)
        {
            return slots[slotIndex].count;
        }
        return 0;
    }
    void ComponentInventoryBase::onSlotChange(uint32_t slotIndex)
    {
        GameSingleton::singletonPtr->getSingleton<SubsystemInventories>()->pushSyncItem(this, slotIndex);
    }
    uint32_t ComponentInventoryBase::getSlotCapacity(uint32_t slotIndex, int32_t cellValue)
    {
        if (slotIndex < slots.size())
        {
            return GameSingleton::blocksManager->blocks[Terrain::ExtractContents(cellValue)]->getMaxStacking(cellValue);
        }
        return 0;
    }
    uint32_t ComponentInventoryBase::getSlotProcessCapacity(uint32_t slotIndex, int32_t cellValue)
    {
        uint32_t slotCount = getSlotCount(slotIndex);
        int32_t slotValue = getSlotValue(slotIndex);
        if (slotCount > 0 && slotValue != 0)
        {
            std::vector<SubsystemBlockBehavior *> &blockBehaviors = GameSingleton::singletonPtr->getSingleton<SubsystemBlockBehaviors>()->getBlockBehaviors(Terrain::ExtractContents(slotValue));
            for (auto behaviorIt = blockBehaviors.begin(); behaviorIt != blockBehaviors.end(); ++behaviorIt)
            {
                int32_t processInventoryItemCapacity = (*behaviorIt)->getProcessInventoryItemCapacity(this, slotIndex, cellValue);
                if (processInventoryItemCapacity > 0)
                {
                    return processInventoryItemCapacity;
                }
            }
        }
        return 0;
    }
    void ComponentInventoryBase::addSlotItems(uint32_t slotIndex, int32_t cellValue, uint32_t count)
    {
        if (addNetSlotItems(slotIndex, cellValue, count) == true)
        {
            onSlotChange(slotIndex);
        }
    }
    bool ComponentInventoryBase::addNetSlotItems(uint32_t slotIndex, int32_t cellValue, uint32_t count)
    {
        if (count > 0 && slotIndex < slots.size())
        {
            std::lock_guard<std::mutex> lk(inventoryMutex);

            if ((getSlotCount(slotIndex) != 0 && getSlotValue(slotIndex) != cellValue) || getSlotCount(slotIndex) + count > getSlotCapacity(slotIndex, cellValue))
            {
                return false;
            }
            slots[slotIndex].value = cellValue;
            slots[slotIndex].count += count;
            return true;
        }
        return false;
    }
    void ComponentInventoryBase::processSlotItems(IInventory *sourceInventory, uint32_t sourceSlotIndex, uint32_t slotIndex, int32_t cellValue, uint32_t count, uint32_t processCount, int32_t &outProcessedValue, uint32_t &outProcessedCount)
    {
        int32_t slotCount = getSlotCount(slotIndex);
        int32_t slotValue = getSlotValue(slotIndex);
        if (slotCount > 0 && slotValue != 0)
        {
            std::vector<SubsystemBlockBehavior *> &blockBehaviors = GameSingleton::singletonPtr->getSingleton<SubsystemBlockBehaviors>()->getBlockBehaviors(Terrain::ExtractContents(slotValue));
            for (auto behaviorIt = blockBehaviors.begin(); behaviorIt != blockBehaviors.end(); ++behaviorIt)
            {
                int32_t processInventoryItemCapacity = (*behaviorIt)->getProcessInventoryItemCapacity(this, slotIndex, cellValue);
                if (processInventoryItemCapacity > 0)
                {
                    (*behaviorIt)->processInventoryItem(this, slotIndex, cellValue, count, processCount, outProcessedValue, outProcessedCount);
                    return;
                }
            }
        }
        outProcessedValue = cellValue;
        outProcessedCount = count;
    }
    uint32_t ComponentInventoryBase::removeSlotItems(uint32_t slotIndex, uint32_t count)
    {
        count = removeNetSlotItems(slotIndex, count);
        if (count > 0)
        {
            onSlotChange(slotIndex);
        }
        return count;
    }
    uint32_t ComponentInventoryBase::removeNetSlotItems(uint32_t slotIndex, uint32_t count)
    {
        if (slotIndex < slots.size())
        {
            std::lock_guard<std::mutex> lk(inventoryMutex);
            count = MathUtils::Min<uint32_t>(count, getSlotCount(slotIndex));
            if (slots[slotIndex].count <= count)
            {
                slots[slotIndex].count = 0;
            }
            else
            {
                slots[slotIndex].count -= count;
            }
            return count;
        }
        return 0;
    }
    void ComponentInventoryBase::dropAllItems(const Vector3 &position)
    {
        static Random random;
        for (int32_t index = 0; index < slots.size(); ++index)
        {
            if (slots[index].value != 0 && slots[index].count > 0)
            {
                dropSlotItems(index, position, (random.getFloat(5.0f, 10.0f) * Vector3::Normalize(Vector3(random.getFloat(-1.0f, 1.0f), random.getFloat(1.0f, 2.0f), random.getFloat(-1.0f, 1.0f)))));
            }
        }
    }
    void ComponentInventoryBase::dropSlotItems(uint32_t slotIndex, const Vector3 &position, const Vector3 &velocity)
    {
        int32_t slotCount = getSlotCount(slotIndex);
        if (slotCount > 0)
        {
            int32_t slotValue = getSlotValue(slotIndex);
            uint32_t num = removeSlotItems(slotIndex, slotCount);
            if (num > 0)
            {
                GameSingleton::singletonPtr->getSingleton<SubsystemPickables>()->addPickable(slotValue, num, position, std::make_shared<Vector3>(velocity), nullptr);
            }
        }
    }

    uint32_t ComponentInventoryBase::getItemCapacityFast(int32_t value)
    {
        uint32_t count = 0;
        uint32_t maxStack = GameSingleton::blocksManager->blocks[Terrain::ExtractContents(value)]->getMaxStacking(value);

        std::lock_guard<std::mutex> lk(inventoryMutex);
        for (Slot &slotItem : slots)
        {
            if (slotItem.count == 0)
            {
                count += maxStack;
            } 
            else if (slotItem.value == value)
            {
                if (slotItem.count < maxStack)
                {
                    count += (maxStack - slotItem.count);
                }
            }
        }
        return count;
    }

    uint32_t ComponentInventoryBase::addItemFast(int32_t value, uint32_t count)
    {
        uint32_t maxStack = GameSingleton::blocksManager->blocks[Terrain::ExtractContents(value)]->getMaxStacking(value);
        std::lock_guard<std::mutex> lk(inventoryMutex);

        for (uint32_t index = 0; index < slots.size(); index++)
        {
            if (getSlotCapacity(index, value) > 0)
            {
                Slot &slotItem = slots[index];
                if (slotItem.count == 0)
                {
                    if (count <= maxStack)
                    {
                        slotItem.value = value;
                        slotItem.count = count;
                        count = 0;
                    }
                    else
                    {
                        slotItem.value = value;
                        slotItem.count = maxStack;
                        count -= maxStack;
                    }
                    onSlotChange(index);
                } 
                else if (slotItem.value == value)
                {
                    if (slotItem.count < maxStack)
                    {
                        uint32_t num = maxStack - slotItem.count;
                        if (count <= num)
                        {
                            slotItem.count += count;
                            count = 0;
                        }
                        else
                        {
                            slotItem.count += num;
                            count -= num;
                        }
                        onSlotChange(index);
                    }
                }
                if (count == 0)
                {
                    break;
                }
            }
        }
        return count;
    }

    int32_t ComponentInventoryBase::FindAcquireSlotForItem(IInventory *inventory, int32_t value)
    {
        for (int32_t i = 0; i < inventory->slotsCount(); i++)
        {
            if (inventory->getSlotCount(i) > 0 && inventory->getSlotValue(i) == value && inventory->getSlotCount(i) < inventory->getSlotCapacity(i, value))
            {
                return i;
            }
        }
        for (int32_t j = 0; j < inventory->slotsCount(); j++)
        {
            if (inventory->getSlotCount(j) == 0 && inventory->getSlotCapacity(j, value) > 0)
            {
                return j;
            }
        }
        return -1;
    }

    int32_t ComponentInventoryBase::AcquireItems(IInventory *inventory, int32_t value, int32_t count)
    {
        while (count > 0)
        {
            int num = FindAcquireSlotForItem(inventory, value);
            if (num < 0)
            {
                break;
            }
            inventory->addSlotItems(num, value, 1);
            count--;
        }
        return count;
    }
    void ComponentInventoryBase::load(const nlohmann::json &jsonData)
    {
        uint32_t slotsCount = jsonData["SlotsCount"].get<uint32_t>();
        slots = std::vector<Slot>(slotsCount, Slot{0, 0});
        auto slotsIt = jsonData.find("Slots");
        if (slotsIt != jsonData.end())
        {
            uint32_t slotCount = slotsIt->size();
            slotCount = slotCount > slotsCount ? slotsCount : slotCount;
            for (int32_t index = 0; index < slotCount; ++index)
            {
                slots[index].value = (*slotsIt)[index]["Contents"].get<int32_t>();
                slots[index].count = (*slotsIt)[index]["Count"].get<uint32_t>();
            }
        }
    }
    void ComponentInventoryBase::save(nlohmann::json &jsonData)
    {
        nlohmann::json slotItemArray = nlohmann::json::array();
        for (auto it = slots.begin(); it != slots.end(); ++it)
        {
            nlohmann::json slotItem;
            if (it->count == 0)
            {
                slotItem["Contents"] = 0;
            }
            else
            {
                slotItem["Contents"] = it->value;
            }
            if (it->value == 0)
            {
                slotItem["Count"] = 0;
            }
            else
            {
                slotItem["Count"] = it->count;
            }
            slotItemArray.push_back(slotItem);
        }
        jsonData["Slots"] = std::move(slotItemArray);
    }
    void ComponentInventoryBase::saveToMsgJson(nlohmann::json &jsonData)
    {
        jsonData["Slots"] = nlohmann::json::object();
        MSG_VALUE_SET(jsonData, "Id", "int", id);

        jsonData["Slots"] = nlohmann::json::object();
        for (int32_t index = 0; index < slots.size(); ++index)
        {
            if (slots[index].value == 0 || slots[index].count == 0)
            {
                continue;
            }
            nlohmann::json slotsJson = nlohmann::json::object();
            MSG_VALUE_SET(slotsJson, "Contents", "int", slots[index].value);
            MSG_VALUE_SET(slotsJson, "Count", "int", slots[index].count);
            jsonData["Slots"][fmt::format("Slot{}", index)] = slotsJson;
        }
    }

}